#ifndef RSAPPIT_CLIENT_WRAPPER
#define RSAPPIT_CLIENT_WRAPPER
#include "clientprotocol_wrapper.h"
#include "udpagent.h"
#include "udpresponse.h"
#include "udprequest.h"
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
#include <errno.h>
#include "randomnumber.h"
#include "config.h"

class SlidingWindow
{
 private:
  int m_head;
  int m_headid;
  int MAX_QUEUE_SIZE;
 public:
  unsigned long long m_bitmap;  /* supports at most 64 bit */
  UdpResponse ** m_array;
  SlidingWindow()
    {
      MAX_QUEUE_SIZE = Config::m_sliding_window;
      m_array = new UdpResponse*[MAX_QUEUE_SIZE];
	  for (int i = 0; i < MAX_QUEUE_SIZE; i++)
		{
		  m_array[i] = NULL;
		}
      m_head = m_headid = 0;
    }
  ~SlidingWindow()
    {
      delete [] m_array;
    }
  bool Enqueue(UdpResponse * pUdp)
  {
    int id = pUdp->ID();
    if (id < m_headid)
      return false;
	int diff = id - m_headid;
    if (diff >= MAX_QUEUE_SIZE) /* out of window */
      return false;
    int pos = (m_head + diff) % MAX_QUEUE_SIZE;
    if (m_array[pos] != NULL)
      {
        return false;
      }
    m_array[pos] = pUdp;
	if (diff < 64)
	  {
		m_bitmap = m_bitmap | ( 1 << diff );
	  }
    return true;
  }
  UdpResponse * Front()
  {
    return m_array[m_head];
  }
  void Pop()
  {
    m_array[m_head] = NULL;
    m_head = (m_head + 1) % MAX_QUEUE_SIZE;
    m_headid ++;
	m_bitmap = m_bitmap >> 1;
  }
  int HeadId()
  {
    return m_headid;
  }
  int Head()
  {
    return m_head;
  }
  void CleanUp()
  {
    for (int i = 0; i < MAX_QUEUE_SIZE; i++)
      {
		if (m_array[i] != NULL)
		  {
			delete (m_array[i]);
			m_array[i] = NULL;
		  }
      }
	m_head = m_headid = 0;
    m_bitmap = 0;
  }
};

struct tagCommand
{
  string strAtVal;
  int offset;
  int limit;
};

struct tagSig
{
  bool bResponded;
  UdpSigRequest * pReq;
  mpz_t * mpzRandInv;
  element_t * emtSig;
};

class ClientUdpWrapper: public ClientProtocolWrapper
{
 public:
  ClientUdpWrapper();

  ~ClientUdpWrapper();

  void ConnectServer(string serverip, unsigned short serverport);

  void ConnectIB(string IBip, unsigned short IBport);

  void CleanUp();

  int RecvSigsFromServer(size_t n, vector<string>& vecsig);
  
  int SendSigRequestsToServer(const vector<struct tagCommand>& vecStrAtVal);

  int SendRequestToIB(string strTag);

  void ReceiveOneBlock(UdpResponse * pUdp, struct sockaddr_in& from);

  void ReceiveOneBlockFromIB(UdpResponse * pUdp);

  void Ack();

  void ReceivingThread();

  void DecryptingThread();

  void OutputingThread();

  void Execute(vector<struct tagCommand> vecStrAtVal);

  virtual int Start();

 protected:
  mpz_t * m_N;
  mpz_t * m_g;
  mpz_t * m_e;

  mpz_t * m_p;
  mpz_t * m_q;
  mpz_t * m_y;
  mpz_t * m_p_minus_1_over_q;  

  element_t * m_emtP;
  element_t * m_emtQ;
  element_t * m_emts;
  pairing_t * m_pairing;

  int m_limit;
  int m_offset;

  RandomNumber m_rand;
  vector<tagSig> m_vecSigs;

  UdpAgent * m_pServer;
  UdpAgent * m_pIB;


  deque<Record *> m_queueToOutput;
  SlidingWindow m_sliding_window;
  Record * m_pRecord;
  map<string, bool> m_hashMap;
  struct
  {
	int m_iRespId;
	int m_iBlockId;
  }m_firstUnreceived;

  unsigned short m_usCurrentRequestId;
  string m_strKi;
  bool m_bThreadExit;
  string m_strTag;
  int m_iTagId;
  string m_strSig;
  int m_iCounter;
  char m_cSigCounter[24];
  string m_strSigCounter;
  struct sockaddr_in m_IBaddr;  /* for sending request */
  
  unsigned short m_port;

  pthread_mutex_t m_mutexNewUdpOrExit;
  pthread_mutex_t m_mutexOutput;
  pthread_mutex_t m_mutexRecord;
  pthread_cond_t m_cvNewUdpOrExit;
  pthread_cond_t m_cvOutput;
  pthread_cond_t m_cvRecord;
  bool m_bBatch;
  bool m_bFirstResp;
#ifdef _MEASURE
  ofstream ofCQC;
  ofstream ofILS;
  ofstream ofRDD;
  ofstream ofBand;
  int m_iResponses;
  unsigned long m_iBytes;
#endif
};


#endif
